﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Microsoft.Services.Clab.UI.Mvvmc.Presentation.Markup
{
    public sealed class ActionCommand : ICommand
    {
        private string actionName;
        private IViewModel viewModel;
        private bool isAutomaticRequeryDisabled;
        private List<WeakReference> canExecuteChangedHandlers;

        public ActionCommand(string actionName, object rootObject)
        {
            this.actionName = actionName;

            var rootControl = rootObject as ContentControl;

            if (rootControl.IsInitialized)
            {
                var dataContextOwner = rootControl.Content as FrameworkElement;
                dataContextOwner.DataContextChanged += DataContextChanged;
                this.Initialize(dataContextOwner);
            }
            else
            {
                rootControl.Initialized += (s, e) =>
                {
                    var dataContextOwner = (s as ContentControl).Content as FrameworkElement;
                    dataContextOwner.DataContextChanged += DataContextChanged;
                    this.Initialize(dataContextOwner);
                };
            }
        }

        #region ICommand Members

        public void Execute(object parameter)
        {
            if (this.viewModel != null && this.viewModel.Controller != null)
            {
                var result = this.viewModel.Controller.TryDoAction(this.actionName, parameter);
                if (!result)
                {
                    CommandManager.InvalidateRequerySuggested();
                }
            }
            else
            {
                throw new InvalidOperationException();
            }
        }

        public bool CanExecute(object parameter)
        {
            if (this.viewModel != null && this.viewModel.Controller != null && this.viewModel.Controller.State < ControllerState.Disposing)
            {
                return this.viewModel.Controller.CanDoAction(this.actionName, parameter);
            }
            else
            {
                return false;
            }
        }

        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (!isAutomaticRequeryDisabled)
                {
                    CommandManager.RequerySuggested += value;
                }
                CommandManagerHelper.AddWeakReferenceHandler(ref canExecuteChangedHandlers, value, 2);
            }
            remove
            {
                if (!isAutomaticRequeryDisabled)
                {
                    CommandManager.RequerySuggested -= value;
                }
                CommandManagerHelper.RemoveWeakReferenceHandler(canExecuteChangedHandlers, value);
            }
        }

        #endregion

        public void RaiseCanExecuteChanged()
        {
            OnCanExecuteChanged();
        }

        public bool IsAutomaticRequeryDisabled
        {
            get
            {
                return isAutomaticRequeryDisabled;
            }
            set
            {
                if (isAutomaticRequeryDisabled != value)
                {
                    if (value)
                    {
                        CommandManagerHelper.RemoveHandlersFromRequerySuggested(canExecuteChangedHandlers);
                    }
                    else
                    {
                        CommandManagerHelper.AddHandlersToRequerySuggested(canExecuteChangedHandlers);
                    }
                    isAutomaticRequeryDisabled = value;
                }
            }
        }

        private void OnCanExecuteChanged()
        {
            DispatcherProxy.BeginInvoke(() =>
            {
                CommandManagerHelper.CallWeakReferenceHandlers(canExecuteChangedHandlers);
            });
        }

        private void DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            this.Initialize(sender as FrameworkElement);
        }

        private void Initialize(FrameworkElement dataContextOwner)
        {
            var ownerFe = dataContextOwner;
            if (ownerFe != null)
            {
                this.viewModel = ownerFe.DataContext as IViewModel;

                if (this.viewModel != null)
                {
                    this.viewModel.Controller.ExecuteWhen<IController>(
                         (name, c) => name == "State" && (c.State >= ControllerState.Initialized || c.State < ControllerState.Disposing),
                         c => this.OnCanExecuteChanged(),
                         false);
                }
            }
        }

        private class CommandManagerHelper
        {
            internal static void CallWeakReferenceHandlers(List<WeakReference> handlers)
            {
                if (handlers != null)
                {
                    // Take a snapshot of the handlers before we call out to them since the handlers
                    // could cause the array to me modified while we are reading it.

                    EventHandler[] callees = new EventHandler[handlers.Count];
                    int count = 0;

                    for (int i = handlers.Count - 1; i >= 0; i--)
                    {
                        WeakReference reference = handlers[i];
                        EventHandler handler = reference.Target as EventHandler;
                        if (handler == null)
                        {
                            // Clean up old handlers that have been collected
                            handlers.RemoveAt(i);
                        }
                        else
                        {
                            callees[count] = handler;
                            count++;
                        }
                    }

                    // Call the handlers that we snapshotted
                    for (int i = 0; i < count; i++)
                    {
                        EventHandler handler = callees[i];
                        handler(null, EventArgs.Empty);
                    }
                }
            }

            internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers)
            {
                if (handlers != null)
                {
                    foreach (WeakReference handlerRef in handlers)
                    {
                        EventHandler handler = handlerRef.Target as EventHandler;
                        if (handler != null)
                        {
                            CommandManager.RequerySuggested += handler;
                        }
                    }
                }
            }

            internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers)
            {
                if (handlers != null)
                {
                    foreach (WeakReference handlerRef in handlers)
                    {
                        EventHandler handler = handlerRef.Target as EventHandler;
                        if (handler != null)
                        {
                            CommandManager.RequerySuggested -= handler;
                        }
                    }
                }
            }

            internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler)
            {
                AddWeakReferenceHandler(ref handlers, handler, -1);
            }

            internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize)
            {
                if (handlers == null)
                {
                    handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>());
                }

                handlers.Add(new WeakReference(handler));
            }

            internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler)
            {
                if (handlers != null)
                {
                    for (int i = handlers.Count - 1; i >= 0; i--)
                    {
                        WeakReference reference = handlers[i];
                        EventHandler existingHandler = reference.Target as EventHandler;
                        if ((existingHandler == null) || (existingHandler == handler))
                        {
                            // Clean up old handlers that have been collected
                            // in addition to the handler that is to be removed.
                            handlers.RemoveAt(i);
                        }
                    }
                }
            }
        }
    }
}
